//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/builtins/builtinfun.h"
#include "sagittarius/session/session.h"
#include "sagittarius/io/io.h"
#include "sagittarius/util/sg_alphanum.h"
#include <string.h>
#include <malloc.h>

void bf_ind(int fd, uint64 ind) {
    uint64 i;
    for(i=0;i<ind;++i)
        SG_WRITE(fd, " ");
}

builtin_fun_ctx* create_builtin_fun_ctx(builtin_type_ctx* bt) {
    sg_fun_val* f;
    builtin_fun_ctx* bf = malloc(sizeof(builtin_fun_ctx));
    
    bf->bt = bt;
    
    // -- empty --
    bf->empty = malloc(sizeof(sg_fun_val));
    bf->empty->t = (sg_type*)bt->fun;
    bf->empty->nargs = 0;
    bf->empty->vd = 0;
    bf->empty->f = bf_empty;
    // empty name-val
    bf->empty_nm = malloc(sizeof(sg_name_val));
    bf->empty_nm->t = (sg_type*)bt->name;
    bf->empty_nm->o = (sg_value*)bf->empty;
    bf->empty_nm->ctx = NULL;
    
    // -- tostr --
    f = malloc(sizeof(sg_fun_val));
    // essential
    f->t = (sg_type*)bt->fun;
    f->nargs = 0;
    f->vd = 0;
    f->f = sg_bf_tostr;
    // optional
    f->s = symtab_get_or_insert_cstr(sg_global_session()->st, "tostr");
    // commit
    bf->tostr = f;
    
    return bf;
}

void install_builtin_fun_syms(builtin_fun_ctx* bf, symtab* st) {
    LexBuf w;
    
    w = sg_lex_str2buf("empty");
    bf->empty_nm->s = symtab_get_or_insertl(st, w, LX_BUFFER_LEN(w)-1).k;
    bf->empty->s = bf->empty_nm->s;
    LX_FREE_BUFFER(w);
}

void release_builtin_fun_ctx(builtin_fun_ctx* bf) {
    free(bf->empty);
}

//-- Builtins --

sg_value* bf_empty(void* ctx) {
    SG_WRITE(2, "bf_empty\n");
    
    sg_value* v = malloc(sg_global_session()->bt->emp->sz);
    sg_init_val(v);
    // type
    v->t = sg_global_session()->bt->emp;
    
    return v;
}

sg_value* sg_bf_tostr(void* ctx, sg_value* self) {
    SG_WRITE(2, "sg_bf_tostr\n");
    sg_str_val* v = malloc(sg_global_session()->bt->str->sz);
    sg_init_val((sg_value*)v);
    
    // set type
    v->t = (sg_type*)sg_global_session()->bt->str;
    
    // init data
    v->w = NULL;
    v->n = 0;
    
    SG_WRITE(2, "self->t = \n");
    sg_writePtr(2, self->t);
    SG_WRITE(2, ", int = ");
    sg_writePtr(2, sg_global_session()->bt->it);
    SG_WRITE(2, "\n");
    
    if(self->t == (sg_type*)sg_global_session()->bt->it) {
        
        sg_int_val* i = sg_cast_int(self);
        char* a = sg_itoa(i->v);
        
        v->w = sg_lex_str2buf(a);
        v->n = LX_BUFFER_LEN(v->w);
        
        free(a);
    }else if(self->t == (sg_type*)sg_global_session()->bt->network) {
        SG_WRITE(2, "tostr network\n");
        {
            sg_network_val* net = sg_cast_to_network(self);
            char* a = sg_network_repr(net);
            
            v->w = sg_lex_str2buf(a);
            v->n = LX_BUFFER_LEN(v->w);
            
            free(a);
        }
    }
    
    return (sg_value*)v;
}

sg_fun_val* bf_create_fun() {
    sg_fun_val* f = malloc(sg_global_session()->bt->fun->sz);
    f->t = (sg_type*)sg_global_session()->bt->fun;
    return f;
}

void bf_release_fun(sg_fun_val* f) {
    free(f);
}

sg_mir_val* bf_create_mir(sg_value* o, symcode s) {
    sg_mir_val* mir = malloc(sg_global_session()->bt->mir->sz);
    mir->t = (sg_type*)sg_global_session()->bt->mir;
    mir->o = o;
    mir->s = s;
    mir->p = NULL;
    return mir;
}

sg_name_val* bf_create_name() {
    sg_name_val* nm = malloc(sg_global_session()->bt->name->sz);
    nm->t = (sg_type*)sg_global_session()->bt->name;
    nm->o = NULL;
    nm->syminst = NULL;
    nm->p = NULL;
    return nm;
}

void bf_release_name(sg_name_val* nm) {
    free(nm);
}

/// bf_dump_name
void bf_dump_name(int fd, sg_name_val* m, symtab* st, uint64 ind) {
    //SG_WRITE(fd, "Name/value ");
    symtabPrintSymK(fd, st, m->s);
    //SG_WRITE(fd, ", index = ");
    SG_WRITE(fd, "[");
    sg_writeUInt64(fd, m->pos);
    //SG_WRITE(fd, "\n");
    SG_WRITE(fd, "]");
}

//-- call --

/*sg_value* bf_do_call(sg_fun_val* f) {
    if(f->vd) {
        AN(0, "void");
    } else {
        switch(f->nargs) {
            case 0:
                return bf_do_call0(f);
            default:
                AN(0, "Unknown # args");
        }
    }
}*/

/// bf_do_call0_vd
void bf_do_call0_vd(sg_fun_val* f) {
    void (*fp)(void) = f->f;
    AT(f->t == (sg_type*)sg_global_session()->bt->fun, "Type must be a function to be callable");
    AT(f->vd, "Function must return void");
    fp();
}

/// bf_do_call0_r
sg_value* bf_do_call0_r(sg_fun_val* f) {
    sg_value* (*fp)(void) = f->f;
    AT(f->t == (sg_type*)sg_global_session()->bt->fun, "Type must be a function to be callable");
    AT(!f->vd, "Function must return a value");
    AT(f->nargs == 0, "Function must have 0 args");
    return fp();
}

/// bf_do_call0
sg_value* bf_do_call0(sg_fun_val* f) {
    if(f->vd) {
        AN(0, "void");
    } else {
        return bf_do_call0(f);
    }
}

/// bf_do_call1_vd
void bf_do_call1_vd(sg_fun_val* f, sg_value* a1) {
    void (*fp)(sg_value*) = f->f;
    AT(SG_TYPE_CHECK(f->t, fun), "Type must be a function to be callable");
    AT(f->vd, "Function must return void");
    fp(a1);
}

/// bf_do_call1_r
sg_value* bf_do_call1_r(sg_fun_val* f, sg_value* a1) {
    sg_value* (*fp)(sg_value*) = f->f;
    AT(SG_TYPE_CHECK(f->t, fun), "Type must be a function to be callable");
    AT(!f->vd, "Function must return a value");
    AT(f->nargs == 0, "Function must have 0 args");
    return fp(a1);
}

/// bf_do_call1
sg_value* bf_do_call1(sg_fun_val* f, sg_value* a1) {
    if(f->vd) {
        AN(0, "void");
    } else {
        return bf_do_call1_r(f, a1);
    }
}

//-- sg_fctx_val --

/// bf_create_fctx
sg_fctx_val* bf_create_fctx() {
    sg_fctx_val* c = malloc(sizeof(sg_fctx_val));
    bf_init_fctx(c);
    return c;
}

void bf_init_fctx(sg_fctx_val* c) {
    c->n = 0;
    c->c = 4;
    c->m = malloc(c->c*sizeof(sg_name_val*));
    c->par = NULL;
    // llvm
    c->ls_locals = NULL;
    c->vlocals2 = NULL;
}

/// bf_release_fctx
void bf_release_fctx(sg_fctx_val* c) {
    uint64 i;
    for(i=0;i<c->n;++i) {
        bf_release_name(c->m[i]);
    }
    free(c->m);
    free(c);
}

/// bf_fctx_grow
void bf_fctx_grow(sg_fctx_val* c) {
    sg_name_val** m;
    c->c <<= 2;
    m = malloc(c->c*sizeof(sg_name_val*));
    memcpy(m, c->m, c->n*sizeof(sg_name_val*));
    free(c->m);
    c->m = m;
}

/// bf_sym_in_fctx
int bf_sym_in_fctx(sg_fctx_val* c, symcode s) {
    uint64 i;
    for(i=0; i<c->n; ++i)
        if(c->m[i]->s == s)
            return 1;
    return 0;
}

/// bf_fctx_get_local
sg_name_val* bf_fctx_get_local(sg_fctx_val* c, symcode s) {
    uint64 i;
    
    for(i=0; i<c->n; ++i)
        if(c->m[i]->s == s)
            return c->m[i];
    if(c->par)
        return bf_fctx_get_local(c->par, s);
    else
        return NULL;
}

/// bf_fctx_insert_sym
sg_name_val* bf_fctx_insert_sym(sg_fctx_val* c, symcode s) {
    sg_name_val* m;
    m = bf_fctx_get_local(c,s);
    if(m)
        return m;
    if(c->n == c->c)
        bf_fctx_grow(c);
    m  = bf_create_name();
    // symbol
    m->s = s;
    // context of definition
    m->ctx = c;
    // object
    m->o = NULL;
    // position
    m->pos = c->n;
    // position relative to other locals
    //m->pos = c->n; // CodeGen_Toplevel::get_locals_type
    // symbol type
    m->sym_tp = SG_SYMTP_NORMAL;
    
    c->m[c->n++] = m;
    return m;
}

/// bf_dump_fctx
void bf_dump_fctx(int fd, sg_fctx_val* c, symtab* st, uint64 ind) {
    uint64 i;
    //bf_ind(fd, ind);
    //SG_WRITE(fd, "Locals: ");
    for(i=0; i<c->n; ++i) {
        if(i!=0)
            SG_WRITE(fd, ", ");
        bf_dump_name(fd, c->m[i], st, ind+2);
    }
    if(c->par) {
        SG_WRITE(fd, "/ ");
        bf_dump_fctx(fd, c->par, st, ind);
    }
    SG_WRITE(fd, "\n");
}

/// bf_do_assn
sg_value* bf_do_assn(sg_value** a, sg_value* b) {
    SG_WRITE(2, "bf_do_assn\n");
    /*builtin_type_ctx* bt = sg_global_session()->bt;
    if(a->t == (sg_type*)bt->name) {
        sg_name_val* m = (sg_name_val*)a;
        return m->o = b;
    } else {
        AN(0, "Unknown assignee type");
        return NULL;
    }*/
    // TODO: case where types are the same: do type-specific assign
    /*builtin_type_ctx* bt = sg_global_session()->bt;
    if(a->t == (sg_type*)bt->syminst) {
        sg_symbol_inst* k = (sg_symbol_inst*)a;
        if(k->o)
            sg_val_decref(k->o);
        sg_val_incref(b);
        k->o = b;
        return b;
    } else {
        AN(0, "Unknown assignee type");
        return NULL;
    }*/
    if(*a)
        sg_val_decref(*a);
    sg_val_incref(b);
    *a = b;
    return b;
}

void sg_push_new_field(sg_value* a, uint64 n) {
    SG_WRITE(2, "sg_push_new_field n = ");
    sg_writeUInt64(2, n);
    SG_WRITE(2, "\n");
    
    sg_value** p;
    uint64 o = n+1;
    //AT(o>0, "Expected field offset");
    p = malloc(o*sizeof(sg_value*));
    if(n>0) {
        memcpy(p, a->p, n*sizeof(sg_value*));
        free(a->p);
    }
    a->p = p;
    // init to null
    a->p[n] = NULL;
    ++a->hc->o;
}

/// bf_do_faccess
sg_value* bf_do_faccessr(sg_value* a, symcode s) {
    SG_WRITE(2, "bf_do_faccessr for ");
    symtabPrintSymK(2, sg_global_session()->st, s);
    SG_WRITE(2, ", hc = ");
    sg_writePtr(2, a->hc);
    if(sg_global_session() && sg_global_session()->pro && sg_global_session()->pro->obj) {
        SG_WRITE(2, ", proto obj hc = ");
        sg_writePtr(2, sg_global_session()->pro->obj->hc);
    }
    SG_WRITE(2, "\n");
    
    sg_type_dtran_val* dv = sg_hc_get_tran(a->hc, s);
    if(a->hc != dv->dst) {
        a->hc = dv->dst;
        sg_push_new_field(a, a->hc->dt->m-1); // or dv->o
    }
    return sg_get_fieldr(a, dv->o);
}

sg_value** bf_do_faccessl(sg_value* a, symcode s) {
    SG_WRITE(2, "bf_do_faccessl for ");
    symtabPrintSymK(2, sg_global_session()->st, s);
    SG_WRITE(2, ", hc = ");
    sg_writePtr(2, a->hc);
    if(sg_global_session() && sg_global_session()->pro && sg_global_session()->pro->obj) {
        SG_WRITE(2, ", proto obj hc = ");
        sg_writePtr(2, sg_global_session()->pro->obj->hc);
    }
    SG_WRITE(2, "\n");
    
    sg_type_dtran_val* dv = sg_hc_get_tran(a->hc, s);
    if(a->hc != dv->dst) {
        a->hc = dv->dst;
        sg_push_new_field(a, a->hc->dt->m-1);
    }
    return sg_get_fieldl(a, dv->o);
}

// sg_value** bf_do_faccessl_freeze(sg_value* a, symcode s) {
//     SG_WRITE(2, "bf_do_faccessl_freeze for ");
//     symtabPrintSymK(2, sg_global_session()->st, s);
//     SG_WRITE(2, "\n");
//     
//     sg_type_dtran_val* dv = sg_hc_tran_auto(a->hc, s);
//     AT(a->hc == dv->dst, "Should be auto-transition");
//     
//     if(dv->o+1 != a->hc->o) {
//         AT(dv->o == a->hc->o, "Field-stacking invariant failed");
//         sg_push_new_field(a, dv->o);
//     }
//     return sg_get_fieldl(a, dv->o);
// }

void sg_dump_fun_val(sg_value* _f, symcode s) {
    sg_fun_val* f = (sg_fun_val*)_f;
    
    SG_WRITE(2, "Function ");
    symtabPrintSymK(2, sg_global_session()->st, s);
    SG_WRITE(2, "\n");
    
    SG_WRITE(2, "f = ");
    sg_writePtr(2, f);
    SG_WRITE(2, ", bf->empty = ");
    sg_writePtr(2, (sg_type*)sg_global_session()->bf->empty);
    SG_WRITE(2, "\n");
    
    SG_WRITE(2, "f->t = ");
    sg_writePtr(2, f->t);
    SG_WRITE(2, ", (sg_type*)sg_global_session()->bt->fun = ");
    sg_writePtr(2, (sg_type*)sg_global_session()->bt->fun);
    SG_WRITE(2, "\n");
    AT(f->t == (sg_type*)sg_global_session()->bt->fun, "Wrong type");
    
    SG_WRITE(2, "Actual sym (0x");
    sg_writeUInt64Hex(2, f->s);
    SG_WRITE(2, ") ");
    symtabPrintSymK(2, sg_global_session()->st, f->s);
    SG_WRITE(2, "\n");
    
    SG_WRITE(2, "f->f = ");
    sg_writePtr(2, f->f);
    SG_WRITE(2, ", bf_empty = ");
    sg_writePtr(2, bf_empty);
    SG_WRITE(2, "\n");
}

void sg_dump_uint64(uint64 i) {
    sg_writeUInt64(2, i);
    SG_WRITE(2, "\n");
}

CReal sg_get_real_scalar(sg_value* obj) {
    if(obj->t == (sg_type*)sg_global_session()->bt->it) {
        sg_int_val* i = sg_cast_int(obj);
        return i->v;
    } else if(obj->t == (sg_type*)sg_global_session()->bt->it) {
        sg_real_val* r = sg_cast_to_real(obj);
        return r->v;
    } else {
        AN(0, "Unknown type");
        return 0.;
    }
}
